package com.hp.bon.sgw.util;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.regex.Pattern;

import javax.crypto.Cipher;

/**
 * 
 * @author wuzh
 */
public class RSAUtil {
	private static final char DIGITS_LOWER[] = { '0', '1', '2', '3', '4', '5',
			'6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
	private static final char DIGITS_UPPER[] = { '0', '1', '2', '3', '4', '5',
			'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

	public static boolean isValidSignal(byte[] dstData, byte[] sign,
			PublicKey puKey) throws Exception {
		Signature sig = Signature.getInstance("MD5WithRSA");
		sig.initVerify(puKey);
		sig.update(dstData);
		return sig.verify(sign);
	}

	public static boolean isValidSignal2(byte[] dstData, byte[] sign2,
			PublicKey puKey, PrivateKey sgwPrivKey) throws Exception {
		// SGW 二次签名
		// SGW拿到原文和数字签名，把16进制的签名转化为流，用RSA算法结合私钥D2对数字签名进行解密，
		// 再用RSA算法结合公钥E1对一次解密后的签名进行二次解密，得到外部网元计算的摘要值
		byte[] ct1 = new byte[64];
		byte[] ct2 = new byte[64];
		System.arraycopy(sign2, 0, ct1, 0, ct1.length);
		System.arraycopy(sign2, ct1.length, ct2, 0, ct2.length);
		byte[] signPart1 = RSAUtil.enCryOrDeCry(ct1, false, sgwPrivKey);
		byte[] signPart2 = RSAUtil.enCryOrDeCry(ct2, false, sgwPrivKey);
		byte[] untiSign = new byte[signPart1.length + signPart2.length];
		System.arraycopy(signPart1, 0, untiSign, 0, signPart1.length);
		System.arraycopy(signPart2, 0, untiSign, signPart1.length,
				signPart2.length);
		//System.out.println("first dec result:"+RSAUtil.byte2hex(untiSign, false));
		//用外部网元公钥进行签名验证
		return RSAUtil.isValidSignal(dstData, untiSign, puKey);
	
	}

	public static byte[] signMessage(byte[] dstData, PrivateKey privKey)
			throws Exception {
		// Signature sig = Signature.getInstance("MD5WithRSA");
		Signature sig = Signature.getInstance("MD5WithRSA");
		sig.initSign(privKey);
		sig.update(dstData);
		return sig.sign();

	}

	/**
	 * 二次签名,用网元私钥签名，并用SGW公钥加密
	 * 
	 * @param dstData
	 * @param privKey
	 * @param sgwPubKey
	 * @return
	 */
	public static byte[] sign2Message(byte[] dstData, PrivateKey privKey,
			PublicKey sgwPubKey) throws Exception {
		byte[] sign = RSAUtil.signMessage(dstData, privKey);
		//byte[] sign = RSAUtil.enCryOrDeCry(md5, true, privKey);
		// 二次签名
		byte[] ct1 = new byte[32];
		byte[] ct2 = new byte[32];
		System.arraycopy(sign, 0, ct1, 0, 32);
		System.arraycopy(sign, 32, ct2, 0, 32);
		byte[] signPart1 = RSAUtil.enCryOrDeCry(ct1, true, sgwPubKey);
		byte[] signPart2 = RSAUtil.enCryOrDeCry(ct2, true, sgwPubKey);
		byte[] totalSign = new byte[signPart1.length + signPart2.length];
		System.arraycopy(signPart1, 0, totalSign, 0, signPart1.length);
		System.arraycopy(signPart2, 0, totalSign, signPart1.length,
				signPart2.length);
		return totalSign;
		// String singal2 = RSAUtil.byte2hex(totalSign, false);

	}

	public static KeyPair readKeyPairFromFiles(String pubFile, String privFile)
			throws Exception {
		return new KeyPair(readPublicKey(pubFile), readPrivateKey(privFile));
	}

	
	public static byte[] enCryOrDeCry(byte[] data, boolean enrypt, Key key)
			throws Exception {
		Cipher rsa_cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
		rsa_cipher
				.init(enrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key);
		byte[] enc_data = rsa_cipher.doFinal(data);
		return enc_data;
	}

	public static PublicKey readPublicKey(String encodedKeyFile)
			throws Exception {
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		FileInputStream fis = new FileInputStream(encodedKeyFile);
		byte[] encodedPublickey = new byte[fis.available()];
		fis.read(encodedPublickey);
		X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encodedPublickey);
		return keyFactory.generatePublic(pubKeySpec);
	}

	/**
	 * 从文本文件中读取公钥，第一行为exponent 数字 第二行为modulus 数字
	 * 
	 * @param keyFile
	 * @return
	 */
	public static PublicKey readPublicKeyFromParamFile(String keyFile)
			throws Exception {
		BigInteger[] params = readKeyParmafromKeyFile(keyFile);
		BigInteger exponent = params[0];
		BigInteger modulus = params[1];

		RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(modulus, exponent);
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		return keyFactory.generatePublic(publicKeySpec);

	}

	private static BigInteger[] readKeyParmafromKeyFile(String keyFile)
			throws Exception {
		LineNumberReader reader = null;
		BigInteger exponent = null;
		BigInteger modulus = null;
		String[] data = new String[2];
		try {
			reader = new LineNumberReader(new InputStreamReader(
					new BufferedInputStream(new FileInputStream(keyFile)),
					"utf-8"));
			String line = null;
			int i = 0;
			while ((line = reader.readLine()) != null) {
				line = line.trim();
				if (!"".equals(line)) {
					data[i++] = line;
				}
				if (i == 2) {
					break;
				}
			}
		} finally {
			if (reader != null) {
				reader.close();
			}
		}

		int radix = isNumeric(data[0]) && isNumeric(data[1]) ? 10 : 16;
		// 第一行是exponent
		exponent = new BigInteger(data[0], radix);
		modulus = new BigInteger(data[1], radix);
		return new BigInteger[] { exponent, modulus };
	}

	public static PrivateKey readPrivateKeyFromParamFile(String keyFile)
			throws Exception {
		BigInteger[] params = readKeyParmafromKeyFile(keyFile);
		BigInteger exponent = params[0];
		BigInteger modulus = params[1];

		RSAPrivateKeySpec privateKeySpec = new RSAPrivateKeySpec(modulus,
				exponent);
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		return keyFactory.generatePrivate(privateKeySpec);

	}

	public static PublicKey readPublicKeyPKCS8(String encodedKeyFile)
			throws Exception {
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		FileInputStream fis = new FileInputStream(encodedKeyFile);
		byte[] encodedPublickey = new byte[fis.available()];
		fis.read(encodedPublickey);
		PKCS8EncodedKeySpec pubKeySpec = new PKCS8EncodedKeySpec(
				encodedPublickey);
		return keyFactory.generatePublic(pubKeySpec);
	}

	private static boolean isNumeric(String str) {
		Pattern pattern = Pattern.compile("[0-9]*");
		return pattern.matcher(str).matches();
	}

	public static PrivateKey readPrivateKey(String encodedKeyFile)
			throws Exception {
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		FileInputStream fis = new FileInputStream(encodedKeyFile);
		byte[] encodedPrivatekey = new byte[fis.available()];
		fis.read(encodedPrivatekey);
		PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(
				encodedPrivatekey);
		return keyFactory.generatePrivate(priPKCS8);
	}

	public static String getReadableMD5Data(String srcData, String encoding) {
		try {
			return getReadableMD5Data(srcData.getBytes(encoding));
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
	}

	public static String getReadableMD5Data(byte[] srcData) {
		return byte2hex(genMD5Bytes(srcData), false);

	}

	public static byte[] genMD5Bytes(byte[] srcData) {
		byte[] dstData = null;
		// 得到一个md5的消息摘要
		MessageDigest alga;
		try {
			alga = MessageDigest.getInstance("MD5");
			// 添加要进行计算摘要的信息
			alga.update(srcData);
			// 得到该摘要
			dstData = alga.digest();
			return dstData;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	// This method reads a certificate to a file. The certificate can be either
	// binary or base64 encoded.
	public static PublicKey readPublicKeyFromX509(String file) throws Exception {
		FileInputStream is = new FileInputStream(file);
		CertificateFactory cf = CertificateFactory.getInstance("X.509");
		java.security.cert.Certificate cert = cf.generateCertificate(is);
		return cert.getPublicKey();
	}

	public static KeyPair loadJavaKeyStore(String caFile, String caAlias,
			String caPassword) throws Exception {

		KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
		caKs.load(new FileInputStream(new File(caFile)),
				caPassword.toCharArray());
		// load the key entry from the keystore
		Key key = caKs.getKey(caAlias, caPassword.toCharArray());
		if (key == null) {
			throw new RuntimeException("Got null key from keystore!");
		}
		RSAPrivateCrtKey privKey = (RSAPrivateCrtKey) key;
		// and get the certificate
		X509Certificate caCert = (X509Certificate) caKs.getCertificate(caAlias);
		if (caCert == null) {
			throw new RuntimeException("Got null cert from keystore!");
		}
		RSAPublicKey pubKey = (RSAPublicKey) caCert.getPublicKey();
		caCert.verify(pubKey);
		return new KeyPair(pubKey, privKey);
	}

	public static void savePublicKeyToParamFormatFile(RSAPublicKey pubkey,
			String file) throws Exception {
		String data = pubkey.getPublicExponent().toString(16) + "\r\n"
				+ pubkey.getModulus().toString(16);
		Comutils.saveToFile(data.getBytes("UTF-8"), file);
	}

	public static void savePrivateKeyToParamFormatFile(RSAPrivateKey privkey,
			String file) throws Exception {
		String data = privkey.getPrivateExponent().toString(16) + "\r\n"
				+ privkey.getModulus().toString(16);
		Comutils.saveToFile(data.getBytes("UTF-8"), file);
	}

	public static void createRSAKeyPair(int keySize, String pubKeyFile,
			String privKeyFile) throws Exception {
		KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");// 指定算法
		kpg.initialize(keySize); // 指定长度
		KeyPair keys = kpg.genKeyPair();
		RSAPublicKey key1 = (RSAPublicKey) keys.getPublic();// 公钥
		RSAPrivateKey key2 = (RSAPrivateKey) keys.getPrivate();// 私钥
		Comutils.saveToFile(key1.getEncoded(), pubKeyFile);
		Comutils.saveToFile(key2.getEncoded(), privKeyFile);
	}

	/**
	 * 将字节数组转化为十六进制字符串
	 * 
	 * @param bytes
	 *            字节数组
	 * @param digits
	 *            数字加字母字符数组
	 * @return
	 */
	public static String byte2hex(byte[] bytes, boolean lowerCase) {
		char[] digits = lowerCase ? DIGITS_LOWER : DIGITS_UPPER;
		// bytes.length << 1肯定是32，左移1位是因为一个字节是8位二进制，一个十六进制用4位二进制表示
		// 当然可以写成l = 32，因为MD5生成的字节数组必定是长度为16的字节数组
		int l = bytes.length << 1;
		char[] rstChars = new char[l];
		int j = 0;
		for (int i = 0; i < bytes.length; i++) {
			// 得到一个字节中的高四位的值
			rstChars[j++] = digits[(0xf0 & bytes[i]) >>> 4];
			// 得到一个字节中低四位的值
			rstChars[j++] = digits[0x0f & bytes[i]];
		}
		return new String(rstChars);
	}

	// 16进制表示的字符串转换成字节数组
	public static byte[] hex2byte(String s) throws Exception {
		char c, c1;
		int x;
		if (s.length() % 2 != 0)
			throw new Exception("密钥格式不正确");
		byte[] ret = new byte[s.length() / 2];
		for (int i = 0; i < s.length(); i++) {
			c = s.charAt(i);
			c1 = s.charAt(++i);
			if (!(c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a'
					&& c <= 'f'))
				throw new Exception("密钥格式不正确");
			if (!(c1 >= '0' && c1 <= '9' || c1 >= 'A' && c1 <= 'F' || c1 >= 'a'
					&& c1 <= 'f'))
				throw new Exception("密钥格式不正确");
			x = Integer.decode("0x" + c + c1).intValue();
			if (x > 127) {
				ret[i / 2] = (byte) (x | 0xffffff00);
			} else {
				ret[i / 2] = (byte) (x);
			}
		}
		return ret;
	}

	public static void main(String[] args) throws Exception {
		String sgwhome = System.getProperty("SGW_HOME");
		if (sgwhome == null) {
			sgwhome = "bin/";
		} else {
			sgwhome += "/bin/";
		}
		String content = "<Service_Information>";
		// AsymmetricCipherKeyPair keyPair = regenRSAKey();
		// readRSAKeyPair(keyFile);
		String md5Bytes = getReadableMD5Data(content.getBytes("iso-8859-1"));
		System.out.println(content + " md5 lenth " + md5Bytes.length()
				+ " value\r\n" + md5Bytes + "\r\n");
		// System.out.println("revert  "
		// + new String(hex2byte(md5Bytes), "iso-8859-1"));
		// createRSAKeyPair(512, "pub.key", "private.key");
		KeyPair keyPair = loadJavaKeyStore(sgwhome + "sgwkeystore", "sgwkey",
				"111111");
		System.out.println("save private key to file ");
		Comutils.saveToFile(keyPair.getPrivate().getEncoded(), sgwhome
				+ "sgw-priv-key");
		String pubKeyParamFile = sgwhome + "sgw-pub-key.param";
		System.out.println("save public key to param (txt )file ");
		savePublicKeyToParamFormatFile((RSAPublicKey) keyPair.getPublic(),
				pubKeyParamFile);
		// PublicKey pubkey=readPublicKeyFromX509("bin/sgw-cert.cer");
		// KeyPair keyPair = readKeyPairFromFiles("pub.key","private.key");
		byte[] sign = RSAUtil.signMessage(content.getBytes(),
				keyPair.getPrivate());
		System.out.println(" hex len " + sign.length + " result \r\n"
				+ byte2hex(sign, false));

		boolean ok = RSAUtil.isValidSignal(content.getBytes(), sign,
				keyPair.getPublic());

		System.out.println(" verify sign " + ok);

		RSAPublicKey pubkey = (RSAPublicKey) readPublicKeyFromX509(sgwhome
				+ "sgwkey-pub.cer");

		ok = RSAUtil.isValidSignal(content.getBytes(), sign, pubkey);
		System.out.println(" verify sign " + ok);

		// BigInteger exponent = pubkey.getPublicExponent();
		// BigInteger modulus = pubkey.getModulus();
		// System.out.println("public key exponent:" + exponent + " modulus "
		// + modulus);
		//
		pubkey = (RSAPublicKey) readPublicKeyFromParamFile(pubKeyParamFile);
		ok = RSAUtil.isValidSignal(content.getBytes(), sign, pubkey);
		System.out.println(" verify sign " + ok);
	} //
}
